/*
 * screen-fade.c
 *
 * Routines to fade the screen for logout dialogs
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * Authors:
 *		Calvin Gaisford <cgaisford@novell.com>
 *
 * Authors for code taken from gnome-panel-logout:
 *		Vincent Untz <vuntz@gnome.org>
 *
 * Authors for code taken from gnome-session-logout:
 *		Owen Taylor <otaylor@redhat.com>
 */

#include <config.h>

#include <string.h>
#include <stdlib.h>

#include <gtk/gtk.h>
#include <gtk/gtkinvisible.h>
#include <gdk/gdkx.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include <gconf/gconf-client.h>

#include <libgnome/libgnome.h>
#include <libgnomeui/libgnomeui.h>

#include "gnome-session-code/gsm-multiscreen.h"
#include "gnome-session-code/gdm-logout-action.h"
#include "gnome-session-code/panel-power-manager.h"

#include "libssui.h"
#include "screen-fade.h"

/* Go for five seconds */
#define FADE_DURATION 1500.0



static GList *fadeout_windows = NULL;
static GList *ionly_windows = NULL;

/* prototypes */
static gboolean fadeout_callback (FadeoutData *fadeout);
static void get_current_frame (FadeoutData *fadeout, double sat);
static void window_get_frame_extents (GdkWindow *window, gint *left,
                                    gint *right, gint *top, gint *bottom);

static void get_current_frame (FadeoutData *fadeout,
		   double    sat)
{
  guchar *sp, *fp;
  int i, j, width, offset;
  const int FIXED = 8;
  int fixed_mult = (0.5 + (sat / 2)) * (1 << 8);

  width = fadeout->area.width * 3;
  offset = 0;
  
  for (i = 0; i < fadeout->area.height; i++)
    {
      sp = fadeout->start_p + offset;
      fp = fadeout->frame_p + offset;

      for (j = 0; j < width; j ++)
	{
	  *(fp++) = (*(sp++) * fixed_mult) >> FIXED;
	}

      offset += fadeout->rowstride;
    }
}

static gboolean fadeout_callback (FadeoutData *fadeout)
{
  GTimeVal current_time;
  double elapsed, percent;

  g_get_current_time (&current_time);
  elapsed = ((((double)current_time.tv_sec - fadeout->start_time.tv_sec) * G_USEC_PER_SEC +
	      (current_time.tv_usec - fadeout->start_time.tv_usec))) / 1000.0;

  if (elapsed < 0)
    {
      g_warning ("System clock seemed to go backwards?");
      elapsed = G_MAXDOUBLE;
    }

  if (elapsed > FADE_DURATION)
    {
      get_current_frame (fadeout, 0.0);
      gdk_draw_pixbuf (fadeout->draw_pixmap,
		       fadeout->gc,
		       fadeout->frame,
		       0, 0,
		       0, 0,
		       fadeout->area.width,
		       fadeout->area.height,
		       GDK_RGB_DITHER_NONE,
		       0, 0);
      gdk_window_clear (fadeout->draw_window);

      fadeout->callback_id = 0;
      return FALSE;
    }

  percent = elapsed / FADE_DURATION;

  get_current_frame (fadeout, 1.0 - percent);
  gdk_draw_pixbuf (fadeout->draw_pixmap,
		   fadeout->gc,
		   fadeout->frame,
		   0, 0,
		   0, 0,
		   fadeout->area.width,
		   fadeout->area.height,
		   GDK_RGB_DITHER_NONE,
		   0, 0);
  gdk_window_clear (fadeout->draw_window);

  gdk_flush ();
  
  return TRUE;
}
  

void ssui_fadeout_screen (GdkScreen *screen, int monitor)
{
	GdkGCValues values;
	GdkWindowAttr attr;
	int attr_mask;
	FadeoutData *fadeout;
	FadeoutData *indata;

	indata = g_new (FadeoutData, 1);

	indata->screen = screen;
	indata->monitor = monitor;

	indata->area.x = gsm_screen_get_x (screen, monitor);
	indata->area.y = gsm_screen_get_y (screen, monitor);
	indata->area.width = gsm_screen_get_width (screen, monitor);
	indata->area.height = gsm_screen_get_height (screen, monitor);

	indata->root_window = gdk_screen_get_root_window (screen);

	attr.window_type = GDK_WINDOW_CHILD;
	attr.x = indata->area.x;
	attr.y = indata->area.y;
	attr.width = indata->area.width;
	attr.height = indata->area.height;
	attr.wclass = GDK_INPUT_ONLY;
	attr.override_redirect = TRUE;
	attr_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;

	indata->draw_window = gdk_window_new (indata->root_window, 
			&attr, attr_mask);
	indata->draw_pixmap = NULL;
	indata->start_pb = NULL;
	indata->frame = NULL;
	indata->gc = NULL;
	indata->callback_id = 0;
	gdk_window_show (indata->draw_window);
	ionly_windows = g_list_prepend (ionly_windows, indata);

	GdkAtom property = gdk_atom_intern ("_NET_WM_STATE_DISPLAY_MODAL", FALSE);
	// if we have setup our little window manager coolness, then
	// just create a GTK_INPUT_ONLY window to handle no input
	if(!gdk_x11_screen_supports_net_wm_hint (screen, property))
	{
		fadeout = g_new (FadeoutData, 1);

		fadeout->screen = screen;
		fadeout->monitor = monitor;

		fadeout->area.x = gsm_screen_get_x (screen, monitor);
		fadeout->area.y = gsm_screen_get_y (screen, monitor);
		fadeout->area.width = gsm_screen_get_width (screen, monitor);
		fadeout->area.height = gsm_screen_get_height (screen, monitor);

		fadeout->root_window = gdk_screen_get_root_window (screen);

		attr.window_type = GDK_WINDOW_TOPLEVEL;
		attr.x = fadeout->area.x;
		attr.y = fadeout->area.y;
		attr.width = fadeout->area.width;
		attr.height = fadeout->area.height;
		attr.wclass = GDK_INPUT_OUTPUT;
		attr.visual = gdk_screen_get_system_visual (fadeout->screen);
		attr.colormap = gdk_screen_get_default_colormap (fadeout->screen);
		attr.override_redirect = FALSE;
		attr_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | 
			GDK_WA_NOREDIR;

		fadeout->draw_window = gdk_window_new (fadeout->root_window, 
				&attr, attr_mask);

		gdk_window_set_decorations(fadeout->draw_window, 0);

		fadeout->draw_pixmap = gdk_pixmap_new (fadeout->draw_window, 
				fadeout->area.width, fadeout->area.height, 
				gdk_drawable_get_depth (fadeout->draw_window));

		fadeout_windows = g_list_prepend (fadeout_windows, fadeout);

		fadeout->start_pb = gdk_pixbuf_get_from_drawable (NULL,
				fadeout->root_window,
				NULL,
				fadeout->area.x,
				fadeout->area.y,
				0, 0,
				fadeout->area.width,
				fadeout->area.height);

		fadeout->frame = gdk_pixbuf_copy (fadeout->start_pb);
		fadeout->rowstride = gdk_pixbuf_get_rowstride (fadeout->start_pb);

		fadeout->start_p = gdk_pixbuf_get_pixels (fadeout->start_pb);
		fadeout->frame_p = gdk_pixbuf_get_pixels (fadeout->frame);

		values.subwindow_mode = GDK_INCLUDE_INFERIORS;

		fadeout->gc = gdk_gc_new_with_values (fadeout->root_window, &values, 
				GDK_GC_SUBWINDOW);

		gdk_window_set_back_pixmap (fadeout->draw_window, NULL, FALSE);
		gdk_window_show (fadeout->draw_window);

		gdk_window_move(fadeout->draw_window, attr.x, attr.y);
		gdk_window_stick(fadeout->draw_window);
		gdk_window_set_keep_above(fadeout->draw_window, TRUE);

		gdk_draw_pixbuf (fadeout->draw_pixmap,
				fadeout->gc,
				fadeout->frame,
				0, 0,
				0, 0,
				fadeout->area.width,
				fadeout->area.height,
				GDK_RGB_DITHER_NONE,
				0, 0);

		gdk_window_set_back_pixmap (fadeout->draw_window, 
				fadeout->draw_pixmap, FALSE);
		gdk_window_clear (fadeout->draw_window);
		g_get_current_time (&fadeout->start_time);

		fadeout->callback_id = g_idle_add ((GSourceFunc) fadeout_callback, 
				fadeout);
	}
}

void ssui_hide_fadeout_windows (void)
{
  GList *l;

  for (l = fadeout_windows; l; l = l->next)
    {
      FadeoutData *fadeout = l->data;
 		gdk_window_hide (fadeout->draw_window);

      if (fadeout->callback_id)
		g_source_remove (fadeout->callback_id);

	  if(fadeout->gc)
      	g_object_unref (fadeout->gc);
	  if(fadeout->start_pb)
      	g_object_unref (fadeout->start_pb);
	  if(fadeout->frame)
      	g_object_unref (fadeout->frame);

	  if(fadeout->draw_window)
      	g_object_unref (fadeout->draw_window);
	  if(fadeout->draw_pixmap)
		g_object_unref (fadeout->draw_pixmap);

      g_free (fadeout);
    }

  g_list_free (fadeout_windows);
  fadeout_windows = NULL;

  for (l = ionly_windows; l; l = l->next)
    {
      FadeoutData *fadeout = l->data;
 		gdk_window_hide (fadeout->draw_window);

      if (fadeout->callback_id)
		g_source_remove (fadeout->callback_id);

	  if(fadeout->gc)
      	g_object_unref (fadeout->gc);
	  if(fadeout->start_pb)
      	g_object_unref (fadeout->start_pb);
	  if(fadeout->frame)
      	g_object_unref (fadeout->frame);

	  if(fadeout->draw_window)
      	g_object_unref (fadeout->draw_window);
	  if(fadeout->draw_pixmap)
		g_object_unref (fadeout->draw_pixmap);

      g_free (fadeout);
    }

  g_list_free (ionly_windows);
  ionly_windows = NULL;
}


void ssui_punch_hole_fadeout_windows (GtkWindow *dialog)
{
	GList *l;
	GdkRectangle    dialogRect, parentRect;
	gint            left, right, top, bottom;
	gint			monitor;
	GdkWindow		*parent;
	GdkOverlapType	overlap;
	GdkRegion		*parentRegion, *dialogRegion;

	// Loop through screens and monitors and punch and fill holes where needed
	for (l = ionly_windows; l; l = l->next)
	{
		FadeoutData *fadeout = l->data;
		parent = fadeout->draw_window;

		// don't use this, it doesn't work with compiz correctly
		//  gdk_window_get_frame_extents(dialog, &dialogRect);

		gdk_window_get_position(GTK_WIDGET(dialog)->window, &dialogRect.x, 
				&dialogRect.y);
		gdk_window_get_geometry(GTK_WIDGET(dialog)->window, NULL, NULL, 
				&dialogRect.width, &dialogRect.height, NULL);
		window_get_frame_extents (GTK_WIDGET(dialog)->window, &left, &right, 
				&top, &bottom);
		dialogRect.x -= left;
		dialogRect.y -= top;
		dialogRect.width += left + right;
		dialogRect.height += top + bottom;

		gdk_window_get_frame_extents(parent, &parentRect);

		// Adjust the dialog rect to be relative to this the parent
		dialogRect.x = dialogRect.x - parentRect.x;
		dialogRect.y = dialogRect.y - parentRect.y;

		// set the parents rect to be relative to this display (0,0)
		parentRect.x = 0;
		parentRect.y = 0;
		parentRegion = gdk_region_rectangle(&parentRect);

		if(parentRegion)
		{
			overlap = gdk_region_rect_in(parentRegion, &dialogRect);
			if(overlap == GDK_OVERLAP_RECTANGLE_OUT)
			{
				// re-protect the parent
				gdk_window_shape_combine_region(parent, NULL, 0, 0);
			}
			else
			{
				dialogRegion = gdk_region_rectangle(&dialogRect);
				if(dialogRegion)
				{
					// subtract the dialog from the parent
					gdk_region_subtract(parentRegion, dialogRegion);
					gdk_window_shape_combine_region(parent, parentRegion, 0, 0);

					gdk_region_destroy(dialogRegion);
					//g_free(dialogRegion);
				}
			}
			gdk_region_destroy(parentRegion);
			//g_free(parentRegion);
		}
	}
}


static void
window_get_frame_extents (GdkWindow *window, gint *left, gint *right,
		gint *top, gint *bottom)
{
	unsigned long *extents = NULL;

	/* some guesses in case this fails to read */
	*left = 6;
	*right = 6;
	*top = 21;
	*bottom = 6;

	/* Attempt to retrieve window's frame extents. */
	if (gdk_property_get (window,
				gdk_atom_intern ("_NET_FRAME_EXTENTS", FALSE),
				gdk_atom_intern ("CARDINAL", FALSE),
				0,
				sizeof (unsigned long) * 4,
				FALSE,
				NULL,
				NULL,
				NULL,
				(guchar **)&extents))
	{
		*left = extents [0];
		*right = extents [1];
		*top = extents [2];
		*bottom = extents [3];
	}
}

